package com.maben.vue_support.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.maben.vue_support.aspect.annotation.LogDbRecord;
import com.maben.vue_support.aspect.pojo.OperateType;
import com.maben.vue_support.pojo.SysLog;
import com.maben.vue_support.util.TokenUtil;
import com.maben.vue_support.service.ISysLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 自动以初解配合切面
 */
@Aspect
@Component
@Slf4j(topic = "m.LogAspect")
public class LogAspect {
    //为了记录方法的执行时间
    final ThreadLocal<Long> startTime = new ThreadLocal<>();
    //操作类型
    final ThreadLocal<OperateType> operateTypeForSave = new ThreadLocal<>();
    @Resource
    private ISysLogService sysLogService;

    /**
     * 设置操作日志切入点，这里介绍两种方式：
     * 1、基于注解切入（也就是打了自定义注解的方法才会切入）
     *
     * @Pointcut("@annotation(com.maben.sun.aspect.LogDbRecord)") 2、基于包扫描切入
     * @Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")
     */
    @Pointcut("@annotation(com.maben.vue_support.aspect.annotation.LogDbRecord)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beforeMethod(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        final Method method = signature.getMethod();
        LogDbRecord annotation = method.getAnnotation(LogDbRecord.class);
        final OperateType operateType = annotation.operateType();
        if (OperateType.OPERATE_TYPE_SAVE.equals(operateType)) {
            final String idFieldName = annotation.idFieldName();
            if (StringUtils.isBlank(idFieldName)) {
                operateTypeForSave.set(OperateType.OPERATE_TYPE_INSERT);
            } else {
                final String params = JSON.toJSONString(joinPoint.getArgs());
                final JSONArray paramsArr = JSONArray.parseArray(params);
                for (Object o : paramsArr) {
                    JSONObject paramsJson = (JSONObject) o;
                    final String idStr = paramsJson.getString(idFieldName);
                    if (StringUtils.isBlank(idStr)) {
                        operateTypeForSave.set(OperateType.OPERATE_TYPE_INSERT);
                    } else {
                        operateTypeForSave.set(OperateType.OPERATE_TYPE_UPDATE);
                    }
                }
            }
        }
    }

    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     */
    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterRequest(JoinPoint joinPoint, Object result) {
        final Long start = startTime.get();
        final long end = System.currentTimeMillis();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            final Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            boolean hasAnnotation = method.isAnnotationPresent(LogDbRecord.class);
            if (hasAnnotation) {
                LogDbRecord annotation = method.getAnnotation(LogDbRecord.class);
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                final String username = TokenUtil.getUsernameFormRequest();
                final SysLog ucSystemLog = new SysLog();
                ucSystemLog.setDescription(annotation.description());
                final OperateType operateType = annotation.operateType();
                setArgs(joinPoint, ucSystemLog);
//                    if (OperateType.OPERATE_TYPE_SAVE.equals(operateType)) {
//                        ucSystemLog.setLogType(operateTypeForSave.get().getDescription());
//                    } else {
//                        ucSystemLog.setLogType(operateType.getDescription());
//                    }
                ucSystemLog.setLogType("INFO");

                // 将入参转换成json
                ucSystemLog.setMethod(methodName); //设置请求方法
                ucSystemLog.setUsername(username);
                ucSystemLog.setRequestIp(getIp(request));
                ucSystemLog.setCreateTime(new Date());
                ucSystemLog.setTime(end - start);
                sysLogService.save(ucSystemLog);
                log.info("log msg:{}",JSON.toJSONString(ucSystemLog));
            }
        } catch (Exception ex) {
            log.info(ExceptionUtils.getStackTrace(ex));
        }
    }


    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        final Long start = startTime.get();
        final long end = System.currentTimeMillis();
        try {
            final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            SysLog ucSystemLog = new SysLog();
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName + "()";
            // 获取操作
            LogDbRecord myLog = method.getAnnotation(LogDbRecord.class);
            if (myLog != null) {
                ucSystemLog.setDescription(myLog.description());
            }
            ucSystemLog.setLogType("ERROR");
            // 将入参转换成json
            ucSystemLog.setMethod(methodName); //设置请求方法
            setArgs(joinPoint, ucSystemLog);
            final String username = TokenUtil.getUsernameFormRequest();
            ucSystemLog.setUsername(username);
            ucSystemLog.setCreateTime(new Date());
            ucSystemLog.setRequestIp(getIp(request));
            final String errMsg = ExceptionUtils.getStackTrace(e);
            if (StringUtils.isNotBlank(errMsg) && errMsg.length() > 3000) {
                ucSystemLog.setExceptionDetail(errMsg.substring(0, 2500));
            } else {
                ucSystemLog.setExceptionDetail(errMsg);
            }
            //插入数据库
            ucSystemLog.setTime(end - start);
            sysLogService.save(ucSystemLog);
            log.error("error msg:{}", JSON.toJSON(ucSystemLog));
        } catch (Exception ex) {
            log.info(ExceptionUtils.getStackTrace(ex));
        }
    }

    /**
     * 设置请求参数
     */
    private void setArgs(JoinPoint joinPoint, SysLog ucSystemLog) {
        final Object[] args = joinPoint.getArgs();
        if (args != null) {
            final String params = JSON.toJSONString(getNameAndValue(joinPoint));
            if (StringUtils.isNotBlank(params) && params.length() > 3000) {
                ucSystemLog.setParams(params.substring(0, 2000));
            } else {
                ucSystemLog.setParams(params);
            }
        }
    }

    /**
     * 获取入参方法参数
     */
    private Map<String, Object> getNameAndValue(JoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>();
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            if (paramValues[i] instanceof MultipartFile || paramValues[i] instanceof HttpSession || paramValues[i] instanceof ServletRequest || paramValues[i] instanceof ServletResponse) {
                param.put(paramNames[i], "忽略请求参数：" + paramValues[i].getClass().getTypeName());
            } else {
                param.put(paramNames[i], paramValues[i]);
            }
        }
        return param;
    }


    /**
     * 根据HttpServletRequest获取访问者的IP地址
     */
    private String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


}
